#include "master.h"
#include "myqueue.h"
#define MSTPRINTF(format, args...) printf("[MASTER] "); printf(format, ##args); printf("\n")


/**
 * simply send empty meta data to slaves, so that they
 * will start their own work;
 */
void start_all_worker(master* m){
    metadata meta;
    meta.type = MSG_ASSIGN_CNF;
    meta.datalen = 0;
    int* r;
    assert(m != NULL);

    while ((r = (int*)pop_queue(m->available_worker)) != NULL){
        MPI_Send(&meta, 1, mpi_metadata_t, *r, 0, MPI_COMM_WORLD);
    }
}

/**
 * receive the complete result from slave, then complete its own
 */
void receive_n_decode_sat_result(master* m, int size, int rank){
    MSTPRINTF("receive_n_decode_sat_result");
    unsigned* encoded = malloc(sizeof(unsigned) * size);
    MPI_Recv(encoded, size, MPI_UNSIGNED , rank, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
    if(m->result_cnf == NULL){
        assert(m->init_cnf != NULL);
        m->result_cnf = new_cnf_from(m->init_cnf);
    }
    MSTPRINTF("receive_n_decode_sat_result got result");
    unset_all_vars(m->result_cnf);
    int name = -1, value = -1;
    for(int i = 0; i < size; i++){
        name = encoded[i] >> 1;
        value = encoded[i] & 0x1;
        _set_variable_value(m->result_cnf, name, value);
    }
    _bool complete = true;
    for (int i = 0; i < m->result_cnf->n_univar; i++){
        if (!m->result_cnf->var_set[i].assigned){
            complete = false;
        }
    }
    if (complete){
        MSTPRINTF("received the sat, completeness check ok"); 
        print_cnf(m->result_cnf);   
    }
    else{
        MSTPRINTF("received the sat, completeness check FAIL");    
        assert(0);
    }
}

/**
 * receive from workers
 */
_bool listen_to_workers(master* m){
    metadata meta;
    MPI_Status status;
    MSTPRINTF("listening");

    MPI_Recv(&meta, 1, mpi_metadata_t, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
    MSTPRINTF("received something");
    switch(meta.type){
    case MSG_FOUND_SOLUTION:
        MSTPRINTF("recv MSG_FOUND_SOLUTION");
        receive_n_decode_sat_result(m, meta.datalen, status.MPI_SOURCE);
        
        //stop them
        metadata meta2;
        meta2.type = MSG_STOP_ALL;
        meta2.datalen = 0;
        for(int i = 1; i < m->n_worker; i++){
            MPI_Send(&meta2, 1, mpi_metadata_t, i, 0, MPI_COMM_WORLD);
            MSTPRINTF("send stop to %d", i);
        }
        

        MSTPRINTF("stop listening");
        return true;    
    default:
        MSTPRINTF("not completed yet %d", meta.type);
        assert(0);
    }

    return false;
}

#define PREPARE_N 10
int names[PREPARE_N];
int signs[PREPARE_N];
int prepare_all_sub_tasks(master* m){
    int tasknum = 0;
    assert(m->init_cnf != NULL);
    assert(m->init_cnf->n_univar > PREPARE_N);

    //clear queue
    if (m->tasks != NULL){
        while(pop_queue(m->tasks) != NULL);
    }
    else{
        assert(0);
    }
    if (m->workmod == TASK_APPOINT_SAME_TACTIC){
        //got names
        for(int i = 0; i < PREPARE_N; i++){
            names[i] = m->init_cnf->var_set[i].name;
            printf("prepare var %d: name: %d\n", i, names[i]);
        }
        for(int x = 0; x < (1<<PREPARE_N); x++){
            for(int i = 0; i < PREPARE_N; i++){
                signs[i] = x & (1 << i);
            }
            unsigned* encoded = malloc(sizeof(unsigned) * PREPARE_N);
            for(int i = 0; i < PREPARE_N; i++){
                encoded[i] = (names[i]<<1) | signs[i];
            }
            tasknum++;

            push_queue(m->tasks, encoded);
        }
    }
    else{
        TODO();
    }

    return tasknum;
}

master* new_master(int rank, int n_worker, cnf* _cnf, int type){
    master* m = malloc(sizeof(master));
    m->rank = rank;
    m->n_worker = n_worker;
    m->init_cnf = _cnf;
    m->workmod = type;
    //set all workers as AVAILABLE, push them in available_worker
    
    m->available_worker = init_queue(m->available_worker);
    if (type == TASK_APPOINT_SAME_TACTIC){
        m->tasks = init_queue(m->tasks);
    }
    assert(m->available_worker!= NULL);
    
    for(int i = 1; i < m->n_worker; i++){
        int* r = malloc(sizeof(int));
        *r = i;
        push_queue(m->available_worker, r);
    }

    return m;
}